/*
 * Copyright (c) 2014-2019 2019 杭州派迩瑞筹信息技术有限公司 All Rights Reserved.
 * ProjectName: pi-cloud
 * FileName: IdUtil.java
 * Author: YuZheng
 * Date: 19-2-19 上午10:43
 * Version: 1.0
 * LastModified: 19-2-19 上午10:43
 */

package com.pisolution.actuator.service.util;
/**
 * 支持2种id, 特点不同, 使用场景不同, 请根据业务情况选择
 *
 * 1. long id, 可以保证全局唯一, 包含: 时间+服务器号+序列号, 多台服务器唯一需设置不同的服务器号, 获取方法getId()
 * 参考twitter snowflake, 生成53bit有序自增id, 41bit timestamp + 5bit serverId + 7bit sequence, javascript数值型最大支持53bit
 * 41bit timestamp 服务器时间, 精确到毫秒
 * 5bit serverid 服务器号, 最多32台, 多台服务器不能重复
 * 7bit sequence 序列号, 1台服务器上1毫秒最多128个id, 1秒128000个id
 *
 * 2. int id, 仅在当前进程中唯一, 服务重启后重新从0开始, 获取方法getIntId();
 *
 * @author yuzheng
 */
public class IdUtil {
    /**
     * 序列号长度7bit
     */
    private static final Integer sequenceBits = 7;
    /**
     * 服务器号长度5bit
     */
    private static final Integer serverIdBits = 5;
    /**
     * 服务器号左移7bit
     */
    private static final Integer serverIdShift = sequenceBits;
    /**
     * 服务器时间左移12bit
     */
    private static final Integer timestampShift = sequenceBits + serverIdBits;
    /**
     * 基准时间, 2010-01-01
     */
    private static final Long baseTimestamp = 1262275200000L;
    /**
     * 服务器时间
     */
    private volatile Long lastTimestamp = -1L;
    /**
     * 序列号
     */
    private volatile Integer sequence = 0;
    /**
     * 序列号长度7bit, 最大128
     */
    private static final Integer sequenceMax = 128;
    /**
     * 单例实例
     */
    private static final IdUtil instance = new IdUtil();

    /**
     * int id, 仅在当前进程中唯一, 服务重启后重新从1开始
     */
    private volatile Integer intId = 1;

    /**
     * 隐藏构造方法
     */
    private IdUtil() {
    }

    /**
     * 应用服务器编号, 多台应用服务器时不能重复
     */
    private static Integer getServerId() {
        return 1;
    }

    /**
     * 获取下一毫秒时间
     *
     * @param lastTimestamp 当前毫秒
     * @return 下一毫秒时间
     */
    private Long getNextMillis(Long lastTimestamp) {
        // 获取下一毫秒
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    /**
     * long id, 可以保证全局唯一, 包含: 时间+服务器号+序列号, 多台服务器唯一需设置不同的服务器号
     *
     * @return 自增Id
     */
    public synchronized Long getId() {
        long timestamp = System.currentTimeMillis();
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) % sequenceMax;
            // 序列号到达128, 时间取下一毫秒
            if (sequence == 0) {
                timestamp = getNextMillis(lastTimestamp);
            }
        } else {
            // 每毫秒序列号清零
            sequence = 0;
        }
        lastTimestamp = timestamp;
        return ((timestamp - baseTimestamp) << timestampShift) | (getServerId() << serverIdShift) | sequence;
    }

    /**
     * 根据传入的时间和serverId获取第一个id，目前在比较数据库中id中用到
     *
     * @param serverId
     * @return
     */
    public synchronized Long getId(Integer serverId) {
        long timestamp = System.currentTimeMillis();
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) % sequenceMax;
            // 序列号到达128, 时间取下一毫秒
            if (sequence == 0) {
                timestamp = getNextMillis(lastTimestamp);
            }
        } else {
            // 每毫秒序列号清零
            sequence = 0;
        }
        lastTimestamp = timestamp;
        return ((timestamp - baseTimestamp) << timestampShift) | (serverId << serverIdShift) | sequence;
    }

    /**
     * 把long id转换成string
     * @param id
     * @return
     */
    public String LongIdToString(Long id) {
        return Long.toHexString(id).toLowerCase();
    }

    public Long stringIdToLong(String idStr) {
        return Long.valueOf(idStr, 16);
    }

    /**
     * int id, 仅在当前进程中唯一, 服务重启后重新从1开始
     *
     * @return
     */
    public synchronized Integer getIntId() {
        return intId++;
    }

    /**
     * 返回单例 IdUtil
     *
     * @return IdUtil
     */
    public static IdUtil getInstance() {
        return instance;
    }
}
